package ijen.fbconnect.helpers;

import ijen.fbconnect.FBRequest;
import ijen.fbconnect.FBResponseObserver;
import ijen.fbconnect.FBSession;
import ijen.fbconnect.services.FBAuthService;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

/**
 * Description of FBLoginHelper.
 * ---------------------------------------------------------------------------------------
 * User: fred
 * Date: 23 juil. 2009
 * Time: 09:46:57
 * ---------------------------------------------------------------------------------------
 */
public final class FBLoginHelper implements FBResponseObserver {

    public enum UserStatus {
        UNKNOWN, LOGGED_IN, DID_NOT_LOG, LOGGED_OUT
    }

    public static final String LOGIN_URI_FBSCRIPT = "https://login.facebook.com/login.php?login_attempt=1&_fb_noscript=1";
    public static final String LOGOUT_URI         = "http://www.facebook.com/logout.php";

    private static final String LOGIN_URI_FORMAT  = "http://login.facebook.com/login.php?";
    private static final String LOGIN_URI_BASE    = "https://login.facebook.com/login.php?login_attempt=1&ijen.fbconnect=1&next=ijen.fbconnect://success";

    private static final HttpContext HTTP_CONTEXT;
    private static final CookieStore COOKIE_STORE;

    static {
        HTTP_CONTEXT = new BasicHttpContext();
        COOKIE_STORE = new BasicCookieStore();
        HTTP_CONTEXT.setAttribute(ClientContext.COOKIE_STORE, COOKIE_STORE);
    }


    private FBAuthService authService;
    private FBSession fbsession;
    private UserStatus userStatus;


    FBLoginHelper() {
        authService = new FBAuthService();
        fbsession = FBSession.getInstance();
        userStatus = UserStatus.UNKNOWN;
    }


    /**
     * <p>Login to FB with a user/pass and an auth token
     * <p>This method keeps a HTTP context (cookies) for maximum compatibility.
     * @param pUser Email adress of user
     * @param pPass Password of user
     * @param pAuthToken Auut token
     */
    public void login(final String pUser, final String pPass, final String pAuthToken) {

        if (pUser == null) {
            throw new IllegalArgumentException("Invalid user");
        }

        if (pPass == null) {
            throw new IllegalArgumentException("Invalid password");
        }

        if (pAuthToken == null) {
            throw new IllegalArgumentException("Invalid token");
        }

        assert COOKIE_STORE != null;

        COOKIE_STORE.clear();   // Clean up all cookies (should attempt a clean logout too)

        userStatus = UserStatus.UNKNOWN;

        // Format login get URL:
        StringBuffer sbUrl = new StringBuffer(LOGIN_URI_FORMAT);
        sbUrl.append("ijen.fbconnect=1")
                .append("&connect_display=touch")
                .append("&api_key=")
                .append(fbsession.getApiKey())
                .append("&next=ijen.fbconnect://success");

        HttpGet get  = new HttpGet(sbUrl.toString());
        HttpPost post = new HttpPost(LOGIN_URI_BASE);

        HttpClient client = getHttpClient();

        executeCall(get, client);       // Execute a first call to FBSession API to setup needed cookies

        List<NameValuePair> params = new ArrayList<NameValuePair>();

        params.add(new BasicNameValuePair("auth_token", pAuthToken));
        params.add(new BasicNameValuePair("api_key", FBSession.getInstance().getApiKey()));

        params.add(new BasicNameValuePair("email", pUser) );
        params.add(new BasicNameValuePair("pass",pPass) );

        try {                   // Attach POST parameters to request

            post.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));

        } catch (UnsupportedEncodingException e) {

            //connected = false;  // Obviously
            throw new RuntimeException("Unable to attach post parameters to login request", e);
        }

        executeCall(post, client);      //  Real authentication attempt to FBSession

        createSession(pAuthToken);
    }


    /**
     * <p>Properly log out from FB
     */
    /*
    public void logout() {

        client = getHttpClient();

        HttpPost post = new HttpPost(LOGOUT_URI);

        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("confirm", "1"));

        try {

            post.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));

        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Unable to attach post parameter to logout request", e);
        }

        executeCall(post);                      // Call logout URL

        COOKIE_STORE.clear();                   // Wipe id cookies
        setSecret(getApplicationSecret());      // Reset client secret to default secret

        connected = false;

    }

    */

    /**
     * <p>Get a fresh http client
     *
     * @return A new HttpClient
     */
    private HttpClient getHttpClient() {
        return new DefaultHttpClient();
    }


    /**
     * <p>Refactoring: Once and Only Once  (3o)
     * <p>Execute a request and consume entity (clear connection).
     * @param pRequest  A request (POST or GET)
     * @param pClient An HTTP client to execute request
     */
    private void executeCall(final HttpRequestBase pRequest, final HttpClient pClient) {

        HttpResponse response;

        try {
            response = pClient.execute(pRequest, HTTP_CONTEXT);

            if (response.getEntity() != null) {
                response.getEntity().consumeContent();
            }

        } catch (IOException e) {

            throw new RuntimeException("Unable to execute request", e);

        }

    }

    /**
     * <p>After user login, we must get a session
     * @param pToken Auth token
     */
    public void createSession(final String pToken) {
        FBRequest req = authService.getSession(pToken);
        req.setObserver(this);

        fbsession.send(req);
    }

    /* -- FBResponseObserver --*/

    /**
     * <p>Process session request called in createSession()
     * @param response Incoming HTTP response (as String)
     */
    public void success(String response) {
        try {
            JSONObject j = new JSONObject(response);
            FBSession.getInstance().start(
                    j.getString("uid"),
                    j.getString("session_key"),
                    j.getString("secret"),
                    j.getLong("expires")
            );
        } catch (JSONException e) {
            throw new RuntimeException("Unable to setup session, check result:" + response, e);
        }

        userStatus = UserStatus.LOGGED_IN;
    }


    public void failure(String response) {
        userStatus = UserStatus.DID_NOT_LOG;
    }


    public void error(String response) {
        userStatus = UserStatus.UNKNOWN;
    }

    public boolean didConnect() {
        return userStatus == UserStatus.LOGGED_IN;
    }

    public UserStatus getUserStatus() {
        return userStatus;
    }
}
